﻿using Axiom.Hermetica.WPF.Models;
using Neocortex.WPF.Hermetica;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml;

namespace Axiom.Hermetica.WPF.ViewModels
{
	public class ViewModel : ViewModelBase
	{
		protected ObservableCollection<ImageSource> _availableThumbnails = new ObservableCollection<ImageSource>();

		protected ObservableCollection<Book> _items = new ObservableCollection<Book>();

		private string _searchTerm = "SEARCH";

		public string SelectedBookPath;

		protected string _selectedCategory;

		public string SelectedCategory
		{
			get
			{
				return _selectedCategory;
			}
			set
			{
				if (_selectedCategory == value)
				{
					return;
				}
				_selectedCategory = value;
				NotifyPropertyChanged("SelectedCategory");
			}
		}

		protected ImageSource _selectedImage;

		protected Book _selectedItem;

		protected ObservableCollection<Book> _selectedItems = new ObservableCollection<Book>();

		public ObservableCollection<ImageSource> AvailableThumbnails
		{
			get { return _availableThumbnails; }
		}

		public IEnumerable<string> Categories
		{
			get
			{
				var all = new List<string>() { "All", "Recent", };
				var cat = (from a in Items select a.Category).Distinct().ToList();
				cat.Sort();
				return all.Union(cat);
			}
		}

		public ObservableCollection<Book> Items
		{
			get { return _items; }
		}

		public string SearchTerm
		{
			get
			{
				return _searchTerm;
			}
			set
			{
				if (_searchTerm == value)
				{
					return;
				}
				_searchTerm = value;
				NotifyPropertyChanged("SearchTerm");
			}
		}

		public ImageSource SelectedImage
		{
			get
			{
				return _selectedImage;
			}
			set
			{
				if (object.ReferenceEquals(_selectedImage, value))
				{
					return;
				}
				_selectedImage = value;
				NotifyPropertyChanged("SelectedImage");
			}
		}

		public Book SelectedItem
		{
			get
			{
				return _selectedItem;
			}
			set
			{
				if (object.ReferenceEquals(_selectedItem, value))
				{
					return;
				}
				_selectedItem = value;
				NotifyPropertyChanged("SelectedItem");
			}
		}

		public ObservableCollection<Book> SelectedItems
		{
			get
			{
				return _selectedItems;
			}
		}

		public async void AddItems(string[] items)
		{
			IsBusy = true;
			Status = "Processing...";
			var books = new List<Book>();

			await Task.Run(() =>
			{
				try
				{
					Parallel.ForEach(items, path =>
					{
						if (!File.Exists(path) && !Directory.Exists(path))
						{
							return;
						}

						if (File.Exists(path))
						{
							AddBookFromFilePath(books, path);
						}
						else if (Directory.Exists(path))
						{
							AddBooksFromDirectory(books, path);
						}
					});

					Status = string.Format("Added {0} books to library", books.Count);
				}
				catch (Exception ex)
				{
					Status = ex.Message.ReadLine();
				}
			});

			books.ForEach(a => Items.Add(a));
			IsBusy = false;
		}

		public void DeleteSelectedItems()
		{
			if (SelectedItem == null)
			{
				return;
			}
			SelectedItems.ToList().ForEach(a => Items.Remove(a));
			SelectedItems.Clear();
			SelectedItem = null;

			NotifyPropertyChanged("Categories");
		}

		public async void GetThumbsAllThumbnailsForItem()
		{
			IsBusy = true;
			Status = "Working";

			AvailableThumbnails.Clear();
			await Task.Run(() =>
			{
				try
				{
					var pdf = new PdfManager(SelectedItem.Path);

					var images = pdf.GetImages();

					foreach (var image in images)
					{
						var imagesource = image.ToImageSource();
						imagesource.Freeze();

						Dispatcher.BeginInvoke(new Action<ImageSource>((a) => AvailableThumbnails.Add(a)), new object[] { imagesource });
					}

					Status = "Ready";
				}
				catch (Exception ex)
				{
					Status = ex.Message.ReadLine();
				}
			});

			IsBusy = false;
		}

		public async void GetThumbsForSelected()
		{
			IsBusy = true;
			Status = "Working";

			await Task.Run(() =>
			{
				try
				{
					var items = new Book[SelectedItems.Count];
					SelectedItems.CopyTo(items, 0);

					foreach (var item in items)
					{
						if (item.Image != null)
						{
							continue;
						}

						var pdf = new PdfManager(item.Path);

						var image = pdf.GetImage();
						if (image == null)
						{
							continue;
						}

						image = image.CreateThumb();
						var imagesource = image.ToImageSource();

						imagesource.Freeze();

						item.Image = imagesource;
					}

					Status = "Ready";
				}
				catch (Exception ex)
				{
					Status = ex.Message.ReadLine();
				}
			});

			IsBusy = false;
		}

		public virtual async void Load()
		{
			IsBusy = true;
			Status = "Loading";
			List<Book> books = null;
			await Task.Run(() =>
			{
				try
				{
					var folder = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

					var path = Path.Combine(folder, "data.dat");

					using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
					{
						var bf = new BinaryFormatter();
						books = bf.Deserialize(stream) as List<Book>;
					}

					Parallel.ForEach(books, a =>
					{
						if (!File.Exists(a.Path))
						{
							a.Remove = true;
						}
					});

					books.RemoveAll(a => a.Remove);

					Parallel.ForEach(books, book =>
					{
						var action = new Action<Book>((a) =>
						{
							Items.Add(a);
						});

						Dispatcher.BeginInvoke(action, new object[] { book });
					});

					ImageService.Load();
					foreach (var id in ImageService.Images.Keys)
					{
						var item = books.FirstOrDefault(a => a.Id == id);
						if (item == null)
						{
							continue;
						}

						var image = ImageService.GetImage(id);
						image.Freeze();
						var action = new Action(() =>
						{
							item.Image = image;
						});

						Dispatcher.BeginInvoke(action, new object[] { });
					}

					books.Where(a => string.IsNullOrWhiteSpace(a.Category)).ToList().ForEach(a => a.Category = "Unknown");

					NotifyPropertyChanged("Categories");
					Status = "Ready";
				}
				catch (Exception ex)
				{
					books = new List<Book>();
					Status = ex.Message.ReadLine();
				}
			});

			IsBusy = false;
		}

		public void OpenSelected()
		{
			ProcessStartInfo startInfo = new ProcessStartInfo() { FileName = SelectedItem.Path };
			Process process = new System.Diagnostics.Process() { StartInfo = startInfo };
			process.Start();
		}

		public async void Save()
		{
			IsBusy = true;
			Status = "Saving";

			await Task.Run(() =>
			{
				try
				{
					var folder = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

					var path = Path.Combine(folder, "data.dat");

					var data = new List<Book>(Items);
					data.Sort((a, b) => a.Name.CompareTo(b.Name));

					using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write))
					{
						var bf = new BinaryFormatter();
						bf.Serialize(stream, data);
					}

					ImageService.Images.Clear();
					foreach (var item in Items)
					{
						if (item.Image == null)
						{
							continue;
						}
						if (item.Image.CanFreeze)
						{
							item.Image.Freeze();
						}
						ImageService.InsertUpdate(item.Id, item.Image);
					}
					ImageService.Persist();

					Status = "Saved";
				}
				catch (Exception ex)
				{
					Status = ex.Message.ReadLine();
				}
				IsBusy = false;
			});

			IsBusy = false;
		}

		public async void SetChecksums()
		{
			IsBusy = true;
			Status = "Working";

			await Task.Run(() =>
			{
				try
				{
					Parallel.ForEach(Items, a =>
					{
						if (a.Checksum == null)
						{

							byte[] hash = null;
							using (var stream = new FileStream(a.Path, FileMode.Open, FileAccess.Read))
							{
								hash = new MD5CryptoServiceProvider().ComputeHash(stream);
							}
							a.Checksum = hash;
						}
					});

					Status = "Ready";
				}
				catch (Exception ex)
				{
					Status = ex.Message.ReadLine();
				}
			});

			IsBusy = false;
		}

		public void UpdateSelectedSet(System.Collections.IList AddedItems, System.Collections.IList RemovedItems)
		{
			if (AddedItems != null && AddedItems.Count > 0)
			{
				var items = new List<Book>(AddedItems.Cast<Book>());

				items.ForEach(a =>
					{
						if (!SelectedItems.Contains(a))
						{
							SelectedItems.Add(a);
						}
					});
			}
			if (RemovedItems != null && RemovedItems.Count > 0)
			{
				var items = new List<Book>(RemovedItems.Cast<Book>());

				items.ForEach(a =>
				{
					if (SelectedItems.Contains(a))
					{
						SelectedItems.Remove(a);
					}
				});
			}
		}

		private void AddBookFromFilePath(List<Book> books, string path)
		{
			if (!Path.GetExtension(path).Equals(".pdf"))
			{
				return;
			}

			var book = new Book()
			{
				Name = Path.GetFileNameWithoutExtension(path).CreateNameFromMangledString(),
				Path = path,
			};

			if (!Items.Any(a => a.Path.Equals(book.Path)))
			{
				books.Add(book);
				Status = string.Format("Processing: {0:00} books", books.Count);
			}
		}

		private void AddBooksFromDirectory(List<Book> books, string folder)
		{
			var files = Directory.GetFiles(folder, "*.pdf", SearchOption.AllDirectories);
			foreach (var path in files)
			{
				AddBookFromFilePath(books, path);
			}
		}

		public Predicate<object> Filter { get; set; }

		public void OpenInExplorer()
		{
			ProcessStartInfo startInfo = new ProcessStartInfo() { FileName = Path.GetDirectoryName(SelectedItem.Path) };
			Process process = new System.Diagnostics.Process() { StartInfo = startInfo };
			process.Start();
		}
	}
}
